/**
 * 1.迷你区块链
 * 2.区块链的生成，新增，校验
 * 3.交易
 * 4.非对称加密
 * 5.挖矿
 * 6.p2p网络
 * 
 * */

// [{
//   index: 索引
//   timestamp：时间戳
//   data：区块具体信息 主要是交易信息
//   hash：当前区块信息的哈希 哈希1
//   preHash：上一个区块的哈希 哈希0
//   nonce：随机数
// }]

const dgram = require('dgram')
const crypto = require('crypto')
const rsa = require('./rsa')
// 创始区块
const initBlock = {
  index: 0,
  data: 'chain',
  prevHash: '0',
  timestamp: 1536622963141,
  nonce: 86746,
  hash: '0000bb957dcb12c7120d1ef73513d108a5c69d13f55c604d282a2f5fec27cf56'
}


class Blockchain {
  constructor() {
    this.blockchain = [
      initBlock
    ]
    this.data = []
    this.difficulty = 4
    // 所有的网络节点信息 IP 端口
    this.peers = []
    // 种子节点
    this.remote = {}
    this.seed = {
      port: 8001,
      address: 'localhost'
    }
    this.udp = dgram.createSocket('udp4')
    this.init()

    const hash = this.computeHash(0, '0', 1551768869271, 'chain', 1)
  }

  init() {
    this.bindP2p()
    this.bindExit()
  }

  bindP2p() {
    this.udp.on('message', (data, remote) => {
      const {
        address,
        port
      } = remote
      const action = JSON.parse(data)
      if (action.type) {
        this.dispatch(action, {
          address,
          port
        })
      }
    })

    this.udp.on('listening', () => {
      const address = this.udp.address()
      console.log('[信息]：udp监听完毕 端口是' + address.port)
    })
    // 区分种子节点和普通节点 普通节点的端口0即可 随便一个空闲端口即可
    // 种子节点端口必须约定好
    // console.log(process.argv)
    const port = Number(process.argv[2]) || 0
    this.startNode(port)
  }

  bindExit() {
    process.on('exit', () => {
      console.log('[信息]：网络一线牵 珍惜这段缘 bye！')
    })
  }

  startNode(port) {
    this.udp.bind(port)
    //如果不是种子节点，需要发送一个消息告诉种子
    if (port !== 8001) {
      this.send({
        type: 'newpeer'
      }, this.seed.port, this.seed.address)
      // 把种子节点加入到本地节点中
      this.peers.push(this.seed)
    }
  }

  send(message, port, address) {
    this.udp.send(JSON.stringify(message), port, address)
  }

  boardcast(action) {
    // 广播全场
    this.peers.forEach(v => {
      this.send(action, v.port, v.address)
    })
  }

  dispatch(action, remote) {
    // 接收到网络的消息在这里处理
    // console.log('接收到p2p网络的消息', action)
    switch (action.type) {
      case 'newpeer':
        //种子节点要做的事情
        // 1.你的公网ip和port是什么
        this.send({
          type: 'remoteAddress',
          data: remote
        }, remote.port, remote.address)
        // 2.现在全部节点的列表
        this.send({
          type: 'peerList',
          data: this.peers
        }, remote.port, remote.address)

        // 3.告诉所有一直节点来了个新朋友来打招呼
        this.boardcast({
          type: 'sayhi',
          data: remote
        })

        // 4.告诉你现在区块链的数据
        this.send({
          type: 'blockchain',
          data: JSON.stringify({
            blockchain: this.blockchain,
            trans: this.data
          })
        }, remote.port, remote.address)
        this.peers.push(remote)
        console.log('你好啊，新朋友，请你喝茶', remote)
        break;

      case 'blockchain':
        // 同步本地链
        let allData = JSON.parse(action.data)
        let newChain = allData.blockchain
        let newTrans = this.data
        this.replaceChain(newChain)
        this.replaceTrans(newTrans)
        break;

      case 'remoteAddress':
        // 存储远程消息
        this.remote = action.data
        break;

      case 'peerList':
        // 远程告诉我 现在的节点列表
        const newPeers = action.data
        this.addPeers(newPeers)
        break;

      case 'sayhi':
        let remotePeer = action.data
        this.peers.push(remotePeer)
        console.log('[信息]： 新朋友你好，相识就是缘分，请你喝茶')
        this.send({
          type: 'hi',
          data: remote
        }, remotePeer.port, remotePeer.addPeers)
        break;

      case 'hi':
        console.log(`${remote.address}:${remote.port}:${action.data}`)
        break;

      case 'trans':
        // 网络收到的交易请求
        // 是不是重复交易
        if (!this.data.find(v => this.isEqualObj(v, action.data))) {
          console.log('有新的交易，请注意查收')
          this.addTrans(action.data)
          this.boardcast({
            type: 'trans',
            data: action.data
          })
        }
        break;

      case 'mine':
        // 网络上有人挖矿成功
        const lastBlock = this.getLastBlock()
        if (lastBlock.hash === action.data.hash) {
          // 重复消息
          return
        }
        if (this.isValidBlock(action.data, lastBlock)) {
          console.log('[信息]：有朋友挖矿成功')
          this.blockchain.push(action.data)
          // 清空本地消息
          this.data = []
          this.boardcast({
            type: 'mine',
            data: action.data
          })
        } else {
          console.log('挖矿区块不合法')
        }
        break;
      default:
        console.log('这个action不认识')
        break;
    }
  }

  isEqualObj(obj1, obj2) {
    const key1 = Object.keys(obj1)
    const key2 = Object.keys(obj2)

    if (key1.length !== key2.length) {
      // key数量不同
      return false
    }
    return key1.every(key => obj1[key] === obj2[key])
  }

  // isEqualPeer(peer1, peer2) {
  //   return peer1.address === peer2.address && peer1.port === peer2.port
  // }

  addPeers(peers) {
    peers.forEach(peer => {
      // 新节点如果不存在 就添加一个到peers
      if (!this.peers.find(v => this.isEqualObj(peer, v))) {
        this.peers.push(peer)
      }
    })
  }

  // 获取最新区块
  getLastBlock() {
    return this.blockchain[this.blockchain.length - 1]
  }

  transfer(from, to, amount) {
    const timestamp = new Date().getTime()

    // 签名校验
    const signature = rsa.sign({
      from,
      to,
      amount,
      timestamp
    })

    const sigTrans = {
      from,
      to,
      amount,
      timestamp,
      signature
    }

    if (from !== '0') {
      const balance = this.balance(from)
      if (balance < amount) {
        console.log(`${from} not enough ${amount} blance only ${balance}`)
        return
      }
      this.boardcast({
        type: 'trans',
        data: sigTrans
      })
    }

    this.data.push(sigTrans)
    return sigTrans
  }


  // 查看余额
  balance(address) {
    let b = 0

    this.blockchain.forEach(block => {
      // console.log(block)
      if (!Array.isArray(block.data)) {
        // 创始区块
        return
      }
      block.data.forEach(trans => {
        if (address == trans.from) {
          b -= trans.amount
        }

        if (address == trans.to) {
          b += trans.amount
        }
      })
    })
    return b
  }

  isValidTransfer(trans) {
    // 是不是合法的转账
    // 地址就是公钥
    return rsa.verify(trans, trans.from)
  }

  addTrans(trans) {
    // console.log(this.isValidTransfer(trans))
    if (this.isValidTransfer(trans)) {
      this.data.push(trans)
    }
  }

  replaceTrans(trans) {
    if (trans.every(v => this.isValidTransfer(v))) {
      this.data = trans
    }
  }
  // 挖矿
  mine(address) {
    // 校验所有交易合法性
    // 只要不合法就报错
    // if (this.data.every(v=>this.isValidTransfer(v))) {
    //   console.log('trans not valid')
    //   return
    // }
    // 过滤不合法
    this.data = this.data.filter(v => this.isValidTransfer(v))

    // 1.生成新区块 新的记账加入了区块链
    // 2.不停的计算哈希 知道计算出符合条件的哈希

    // 挖矿结束 矿工奖励 每次挖矿成功
    this.transfer('0', address, 100)
    const newBlock = this.generateNewBlock()

    // 区块合法并且区块链合法新增
    if (this.isValidBlock(newBlock) && this.isValidChain()) {
      this.blockchain.push(newBlock)
      this.data = []
      console.log('[信息]：挖矿成功')
      this.boardcast({
        type: 'mine',
        data: newBlock
      })
      return newBlock
    } else {
      console.log('error, invalid Block faile')
      return
    }

  }

  // 生成新区块
  generateNewBlock() {
    let nonce = 0
    const index = this.blockchain.length
    const data = this.data
    const prevHash = this.getLastBlock().hash
    let timestamp = new Date().getTime()
    let hash = this.computeHash(index, prevHash, timestamp, data, nonce)
    while (hash.slice(0, this.difficulty) !== '0'.repeat(this.difficulty)) {
      nonce += 1
      hash = this.computeHash(index, prevHash, timestamp, data, nonce)
    }
    return {
      index,
      data,
      prevHash,
      timestamp,
      nonce,
      hash
    }
  }

  // 计算哈希
  computeHash(index, preHash, timestamp, data, noce) {
    return crypto
      .createHash('sha256')
      .update(index + preHash + timestamp + data + noce)
      .digest('hex')
  }

  // 校验区块
  isValidBlock(newBlock, lastBlock = this.getLastBlock()) {
    // 1.区块的index等于最新区块的index+1
    // 2. 区块的time大于最新
    // 3.最新的prevHash等于最新区块的hash
    // 4.区块的哈希值符合难度要求
    //   console.log(newBlock)
    // console.log(lastBlock)
    if (newBlock.index !== lastBlock.index + 1) {
      return false
    } else if (newBlock.timeStamp <= lastBlock.timestamp) {
      return false
    } else if (newBlock.prevHash !== lastBlock.hash) {
      return false
    } else if (newBlock.hash.slice(0, this.difficulty) !== '0'.repeat(this.difficulty)) {
      return false
    } else {
      return true
    }
  }

  // 校验区块链
  isValidChain(chain = this.blockchain) {
    for (let i = chain.length - 1; i >= 1; i--) {
      if (!this.isValidBlock(chain[i], chain[i - 1])) {
        return false
      }
    }
    if (JSON.stringify(chain[0]) !== JSON.stringify(initBlock)) {
      return false
    }
    return true
  }

  replaceChain(newChain) {
    // 先不校验交易
    if (newChain.length === 1) {
      return
    }
    if (this.isValidChain(newChain) && newChain.length > this.blockchain.length) {
      // 拷贝一份（深拷贝）
      this.blockchain = JSON.parse(JSON.stringify(newChain))
    } else {
      console.log('[错误]:不合法')
    }
  }
}


// let bc = new Blockchain()
// bc.mine()
// bc.mine()
// bc.mine()

// console.log(bc.blockchain)

module.exports = Blockchain